﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace SpiderMonkey
{
    public delegate bool PropertyOperation(Context context, JSObject obj, JSId id, ref JSVal value);
    public delegate bool StrictPropertyOperation(Context context, JSObject obj, JSId id, bool strict, ref JSVal value);
    public delegate bool EnumerateOperation(Context context, JSObject obj);
    public delegate bool ResolveOperation(Context context, JSObject obj, JSVal id);
    public delegate bool ConvertOperation(Context context, JSObject obj, JSType type, out JSVal value);
    public delegate void FinalizeOperation(Context context, JSObject obj);

    public sealed class Class
    {
        private static Dictionary<string, Class> classes = new Dictionary<string, Class>();
        private static Native.JSPropertyOp nativeAddProperty = AddJSPropertyOp;
        private static Native.JSPropertyOp nativeDelProperty = DelJSPropertyOp;
        private static Native.JSPropertyOp nativeGetProperty = GetJSPropertyOp;
        private static Native.JSStrictPropertyOp nativeSetProperty = JSStrictPropertyOp;
        private static Native.JSEnumerateOp nativeEnumerate = JSEnumerateOp;
        private static Native.JSResolveOp nativeResolve = JSResolveOp;
        private static Native.JSConvertOp nativeConvert = JSConvertOp;
        private static Native.JSFinalizeOp nativeFinalize = JSFinalizeOp;

        private string name;
        private ClassFlags flags;

        public PropertyOperation AddProperty;
        public PropertyOperation DelProperty;
        public PropertyOperation GetProperty;
        public StrictPropertyOperation SetProperty;
        public EnumerateOperation Enumerate;
        public ResolveOperation Resolve;
        public ConvertOperation Convert;
        public FinalizeOperation Finalize;

        public Class(string name, ClassFlags flags)
        {
            this.name = name;
            this.flags = flags;
            AddProperty = PropertyStub;
            DelProperty = PropertyStub;
            GetProperty = PropertyStub;
            SetProperty = StrictPropertyStub;
            Enumerate = EnumerateStub;
            Resolve = ResolveStub;
            Convert = ConvertStub;
            Finalize = FinalizeStub;

            classes[name] = this;
        }

        public IntPtr ToNative()
        {
            IntPtr ptr = Marshal.AllocHGlobal(IntPtr.Size * 36 + sizeof(int));
            //These may be memory leaks
            IntPtr cName = Marshal.StringToHGlobalAnsi(name);
            IntPtr addProperty = Marshal.GetFunctionPointerForDelegate(nativeAddProperty);
            IntPtr delProperty = Marshal.GetFunctionPointerForDelegate(nativeDelProperty);
            IntPtr getProperty = Marshal.GetFunctionPointerForDelegate(nativeGetProperty);
            IntPtr setProperty = Marshal.GetFunctionPointerForDelegate(nativeSetProperty);
            IntPtr enumerate = Marshal.GetFunctionPointerForDelegate(nativeEnumerate);
            IntPtr resolve = Marshal.GetFunctionPointerForDelegate(nativeResolve);
            IntPtr convert = Marshal.GetFunctionPointerForDelegate(nativeConvert);
            IntPtr finalize = Marshal.GetFunctionPointerForDelegate(nativeFinalize);

            Marshal.WriteIntPtr(ptr, 0, cName);
            Marshal.WriteInt32(ptr, IntPtr.Size, (int)flags);
            Marshal.WriteIntPtr(ptr, IntPtr.Size + sizeof(int), addProperty);
            Marshal.WriteIntPtr(ptr, IntPtr.Size * 2 + sizeof(int), delProperty);
            Marshal.WriteIntPtr(ptr, IntPtr.Size * 3 + sizeof(int), getProperty);
            Marshal.WriteIntPtr(ptr, IntPtr.Size * 4 + sizeof(int), setProperty);
            Marshal.WriteIntPtr(ptr, IntPtr.Size * 5 + sizeof(int), enumerate);
            Marshal.WriteIntPtr(ptr, IntPtr.Size * 6 + sizeof(int), resolve);
            Marshal.WriteIntPtr(ptr, IntPtr.Size * 7 + sizeof(int), convert);
            Marshal.WriteIntPtr(ptr, IntPtr.Size * 8 + sizeof(int), finalize);

            for (int i = 0; i < 27; ++i)
            {
                Marshal.WriteIntPtr(ptr, IntPtr.Size * (9 + i) + sizeof(int), IntPtr.Zero);
            }

            return ptr;
        }

        private static bool PropertyStub(Context context, JSObject obj, JSId id, ref JSVal value)
        {
            return true;
        }
        private static bool StrictPropertyStub(Context context, JSObject obj, JSId id, bool strict, ref JSVal value)
        {
            return true;
        }
        private static bool EnumerateStub(Context context, JSObject obj)
        {
            return true;
        }
        private static bool ResolveStub(Context context, JSObject obj, JSVal id)
        {
            return true;
        }
        private static bool ConvertStub(Context context, JSObject obj, JSType type, out JSVal value)
        {
            value = obj.ToValue();
            return true;
        }
        private static void FinalizeStub(Context context, JSObject obj)
        {
        }

        private static int AddJSPropertyOp(IntPtr cx, IntPtr obj, IntPtr id, IntPtr vp)
        {
            /*IntPtr nativeCls = Native.JS_GetClass(obj);
            string name = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(nativeCls, 0));
            Class cls = classes[name];

            Context context = Context.Reference(cx);
            JSObject clrObj = JSObject.Reference(obj);
            JSId clrId = new JSId(id);
            long vpVal = Marshal.ReadInt64(vp);
            JSVal val = new JSVal((ulong)vpVal);

            bool ret = cls.AddProperty(context, clrObj, clrId, ref val);
            Marshal.WriteInt64(vp, (long)val.Pointer);

            return ret ? 1 : 0;*/
            return 1;
        }
        private static int DelJSPropertyOp(IntPtr cx, IntPtr obj, IntPtr id, IntPtr vp)
        {
            IntPtr nativeCls = Native.JS_GetClass(obj);
            string name = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(nativeCls, 0));
            Class cls = classes[name];

            Context context = Context.Reference(cx);
            JSObject clrObj = JSObject.Reference(obj);
            JSId clrId = new JSId(id);
            long vpVal = Marshal.ReadInt64(vp);
            JSVal val = new JSVal((ulong)vpVal);

            bool ret = cls.DelProperty(context, clrObj, clrId, ref val);
            Marshal.WriteInt64(vp, (long)val.Pointer);

            return ret ? 1 : 0;
        }
        private static int GetJSPropertyOp(IntPtr cx, IntPtr obj, IntPtr id, IntPtr vp)
        {
            IntPtr nativeCls = Native.JS_GetClass(obj);
            string name = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(nativeCls, 0));
            Class cls = classes[name];

            Context context = Context.Reference(cx);
            JSObject clrObj = JSObject.Reference(obj);
            JSId clrId = new JSId(id);
            long vpVal = Marshal.ReadInt64(vp);
            JSVal val = new JSVal((ulong)vpVal);

            bool ret = cls.GetProperty(context, clrObj, clrId, ref val);
            Marshal.WriteInt64(vp, (long)val.Pointer);

            return ret ? 1 : 0;
        }
        private static int JSStrictPropertyOp(IntPtr cx, IntPtr obj, IntPtr id, int strict, IntPtr vp)
        {
            IntPtr nativeCls = Native.JS_GetClass(obj);
            string name = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(nativeCls, 0));
            Class cls = classes[name];

            Context context = Context.Reference(cx);
            JSObject clrObj = JSObject.Reference(obj);
            JSId clrId = new JSId(id);
            long vpVal = Marshal.ReadInt64(vp);
            JSVal val = new JSVal((ulong)vpVal);

            bool ret = cls.SetProperty(context, clrObj, clrId, strict != 0, ref val);
            Marshal.WriteInt64(vp, (long)val.Pointer);

            return ret ? 1 : 0;
        }
        private static int JSEnumerateOp(IntPtr cx, IntPtr obj)
        {
            IntPtr nativeCls = Native.JS_GetClass(obj);
            string name = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(nativeCls, 0));
            Class cls = classes[name];

            Context context = Context.Reference(cx);
            JSObject clrObj = JSObject.Reference(obj);

            bool ret = cls.Enumerate(context, clrObj);

            return ret ? 1 : 0;
        }
        private static int JSResolveOp(IntPtr cx, IntPtr obj, ulong id)
        {
            IntPtr nativeCls = Native.JS_GetClass(obj);
            string name = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(nativeCls, 0));
            Class cls = classes[name];

            Context context = Context.Reference(cx);
            JSObject clrObj = JSObject.Reference(obj);
            JSVal val = new JSVal(id);

            bool ret = cls.Resolve(context, clrObj, val);

            return ret ? 1 : 0;
        }
        private static int JSConvertOp(IntPtr cx, IntPtr obj, JSType type, IntPtr vp)
        {
            IntPtr nativeCls = Native.JS_GetClass(obj);
            string name = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(nativeCls, 0));
            Class cls = classes[name];

            Context context = Context.Reference(cx);
            JSObject clrObj = JSObject.Reference(obj);

            JSVal val;
            bool ret = cls.Convert(context, clrObj, type, out val);
            Marshal.WriteInt64(vp, (long)val.Pointer);

            return ret ? 1 : 0;
        }
        private static void JSFinalizeOp(IntPtr cx, IntPtr obj)
        {
            IntPtr nativeCls = Native.JS_GetClass(obj);
            string name = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(nativeCls, 0));
            Class cls = classes[name];

            Context context = Context.Reference(cx);
            JSObject clrObj = JSObject.Reference(obj);

            cls.Finalize(context, clrObj);
        }
    }
}
